﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using Gumini.JulaDB.DataTypes;
using Gumini.JulaDB.Expressions;
using Gumini.JulaDB.Interfaces;

namespace Gumini.JulaDB.Data
{
    /// <summary>
    /// Represent a row of a table and contains data.
    /// This is a lightweight class that stores all of its data in an array and allows random access.
    /// </summary>
    [Serializable]
    public class Row : IEnumerable<object>, ISerializable
    {
        readonly object[] _values;

        /// <summary>
        /// Initializes a new instance of the <see cref="Row"/> class with fixed side and empty data.
        /// </summary>
        /// <param name="numberOfColumns">The number of columns.</param>
        public Row(int numberOfColumns)
        {
            _values = new object[numberOfColumns];
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Row"/> class. Fills the new row with the data from two rows passed as parameters.
        /// </summary>
        /// <param name="a">First row.</param>
        /// <param name="b">Second row.</param>
        public Row(Row a, Row b)
            : this(a._values.Length + b._values.Length)
        {
            for (int i = 0; i < a._values.Length; i++)
                _values[i] = a._values[i];
            for (int i = 0; i < b._values.Length; i++)
                _values[a._values.Length + i] = b._values[i];
        }

        public object this[int columnOrdinal]
        {
            get { return _values[columnOrdinal]; }
            private set { _values[columnOrdinal] = value; }
        }

        internal void Assign(int columnOrdinal, DataType targetType, object sourceValue)
        {
            // we ensure if the actual value was passed here and not the TypedValue wrapper
            Debug.Assert(sourceValue == null || sourceValue.GetType() != typeof(TypedValue));

            this[columnOrdinal] = sourceValue != null ? targetType.ConvertFrom(sourceValue) : null;
        }

        public Context<TypedValue> GetEvaluationContext(IHeading<IColumn> heading)
        {
            // creating an evaluation context with all field names and values
            var context = new Context<TypedValue>();
            for (int i = 0; i < heading.NumberOfColumns; i++)
            {
                var definition = heading[i];
                foreach(var name in definition.AllNames)
                    context.Add(name, new TypedValue(definition.Type, this[i]));
            }
            return context;
        }

        public int NumberOfColumns
        {
            get { return _values.Length; }
        }

        public override string ToString()
        {
            return string.Format("({0})", string.Join(", ", _values.Select(c => c ?? "null").ToArray()));
        }

        public IEnumerator<object> GetEnumerator()
        {
            return ((IEnumerable<object>)_values).GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _values.GetEnumerator();
        }

        #region Serialization

        private const string ValuesName = "_values";

        public Row(SerializationInfo info, StreamingContext context)
        {
            _values = (object[])info.GetValue(ValuesName, typeof(object[]));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(ValuesName, _values, typeof(object[]));
        }

        #endregion
    }
}
